﻿
//////////////////////////////////////////////////////////////////////////////
//
// Copyright © 1998-2024 Glodon Company Limited.
//
// Permission is hereby granted, free of charge, to any person obtaining a
// copy of this software and associated documentation files (the “Software”),
// to deal in the Software without restriction, including without limitation
// the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
// sell copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
// THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
//
//////////////////////////////////////////////////////////////////////////////

#include "CmdSnapWithSnapper.h"
#include "ISnap.h"
#include "IPlane.h"
#include "IUiView.h"
#include "ILine3d.h"
#include "Snapper.h"
#include "IAction.h"
#include "Vector3d.h"
#include "IElement.h"
#include "IDocument.h"
#include "ActionBase.h"
#include "IModelView.h"
#include "IModelLine.h"
#include "IWorkPlane.h"
#include "IPickFilter.h"
#include "IUiDocument.h"
#include "UiSnapUtils.h"
#include "IPickTarget.h"
#include "ISnapRender.h"
#include "ISnapContext.h"
#include "IGraphicsNode.h"
#include "Vector3dUtils.h"
#include "ISnapSettings.h"
#include "UiCommonDialog.h"
#include "IGraphicsPoint.h"
#include "IGraphicsPlane.h"
#include "ISnapCandidates.h"
#include "GcmpCommandState.h"
#include "IGraphicsCurve3d.h"
#include "IUserTransaction.h"
#include "IGraphicsNodeGroup.h"
#include "IElementModelShape.h"
#include "UiDocumentViewUtils.h"
#include "IGraphicsElementShape.h"
#include "IGraphicsNodeReference.h"
#include "GraphicsNodeAndTransform.h"
#include "ITemporaryDimensionContext.h"
#include "ITemporaryDimensionManager.h"

#include "SnapFilterDlg.h"
#include "CommandRegister.h"
#include "SampleUIUtils.h"
#include "PickSnapCommandIds.h"
#include "CmdSnapModuleEntry.h"
#include "EnableCompileWarning_The_LAST_IncludeInCpp.h" 

using namespace gcmp;
using namespace Sample;

namespace Sample
{
#pragma region 捕捉过滤器
    // 捕捉过滤器
    class SnapWithSnapperPickFilter : public IPickFilter
    {
    public:
        SnapWithSnapperPickFilter()
        {
        }

        virtual bool AllowElement(const gcmp::ElementId& elementId) const override
        {
            return true;
        }

        virtual bool AllowGraphicsNode(const gcmp::IGraphicsNodeReference& graphicsNodeReference) const override
        {
            return true;
        }

        virtual bool SetPickTargetOption(gcmp::IPickTarget* pPickTarget) override
        {
            pPickTarget->EnableAll();
            return true;
        }
    };
#pragma endregion 捕捉过滤器

#pragma region 使用IPickPointAction的Action

    class SnapPointAction : public gcmp::ActionBase
    {
    public:
        /// \brief 回调函数
        ///
        /// \param pCurrentView    当前Ui视图
        /// \param pickedPt  Pick到的点(射线与工作平面的交点)
        /// \param mousePt   鼠标点
        typedef std::function<void(gcmp::IUiView* pCurrentView, const gcmp::Vector3d& pickedPt, const gcmp::Vector3d& mousePt)> MoveCallback;

        /// \brief 创建回调函数
        /// \param *func    T对象的函数指针
        /// \param object  T对象
        template<typename T>
        static MoveCallback CreateMoveCallback(
            void (T::*func)(gcmp::IUiView*, const gcmp::Vector3d& pos, const gcmp::Vector3d& mousePt),
            T* object)
        {
            if (object)
            {
                return std::bind(func, object, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3);
            }
            return nullptr;
        }

        /// 设置捕捉光标
        virtual std::wstring GetCursorPath() const override
        {
            if (CmdSnapModuleEntry::DoCustomizeCursor())
            {
                return L":/images/Cursor/cross_big.png";
            }
            return L"";
        }
    public:
        SnapPointAction(MoveCallback moveCallback, Vector3d& snappedPoint, Vector3d* pFirstPoint = nullptr)
            : m_moveCallback(moveCallback), m_snappedPoint(snappedPoint), m_pFirstPoint(pFirstPoint)
        {
            m_snapFilterDlg = SnapFilterDlg::Get();
            DBG_WARN_AND_RETURN_VOID_UNLESS(m_snapFilterDlg, L"m_snapFilterDlg为空",L"GDMP",L"2023-12-30");
        }
        
        ~SnapPointAction()
        {
            // Snapper需要人工维护捕捉渲染的创建和清除
            ISnapRender::Get()->ClearSnapRenderData();
            UpdateView();
        }

        virtual void InitAction(IUiView* pCurrentView) override
        {
            // 创建opSnapContext，设置近程捕捉和远程捕捉的过滤器
            m_opSnapContext = ISnapContext::Create();
            DBG_WARN_AND_RETURN_VOID_UNLESS(m_opSnapContext, L"m_opSnapContext为空",L"GDMP",L"2023-12-30");
            m_opLocalPickFilter = NEW_AS_OWNER_PTR(SnapWithSnapperPickFilter);
            DBG_WARN_AND_RETURN_VOID_UNLESS(m_opLocalPickFilter, L"m_opLocalPickFilter为空",L"GDMP",L"2023-12-30");
            m_opRemotePickFilter = NEW_AS_OWNER_PTR(SnapWithSnapperPickFilter);
            DBG_WARN_AND_RETURN_VOID_UNLESS(m_opRemotePickFilter, L"m_opRemotePickFilter为空",L"GDMP",L"2023-12-30");
            m_opSnapContext->SetFilterForLocalSnap(m_opLocalPickFilter.get());
            m_opSnapContext->SetFilterForRemoteSnap(m_opRemotePickFilter.get());

            ISnapSettings* pGlobalSnapSettings = ISnapSettings::GetGlobalSnapSettings();
            DBG_WARN_AND_RETURN_VOID_UNLESS(pGlobalSnapSettings, L"pGlobalSnapSettings为空",L"GDMP",L"2023-12-30");
            m_opSnapSettings = pGlobalSnapSettings->Clone();
            DBG_WARN_AND_RETURN_VOID_UNLESS(m_opSnapSettings, L"m_opSnapSettings为空",L"GDMP",L"2023-12-30");

            if (m_pFirstPoint)
            {
                m_opSnapContext->SetFirstPoint(m_pFirstPoint);
            }
        }

        virtual bool OnLButtonDown(IUiView* pCurrentView, const Vector3d& pos) override
        {
            MarkFinishStatus(ActionFinishStatus::Successful);
            return true;
        }

        virtual bool OnMovePoint(IUiView* pCurrentView, const Vector3d& pos) override
        {
            // 清除上一次捕捉临时渲染
            ISnapRender* pSnapRender = ISnapRender::Get();
            DBG_WARN_AND_RETURN_FALSE_UNLESS(pSnapRender, L"pSnapRender为空",L"GDMP",L"2023-12-30");
            pSnapRender->ClearSnapRenderData();
            UpdateView();

            // 实时更新捕捉设置，这样用户可以随时修改捕捉设置
            m_snapFilterDlg->SetSnapSettings(m_opSnapSettings.get());
            m_opSnapContext->SetSnapSettings(m_opSnapSettings.get());
            m_snapFilterDlg->SetSnapContext(m_opSnapContext.get());

            int tolerance = CmdSnapModuleEntry::GetTolerance();
            m_opSnapSettings->SetSnapDistanceForLocalSnap(tolerance);
            m_opSnapSettings->SetSnapDistanceForRemoteSnap(tolerance);
            m_opSnapSettings->SetSnapDistanceForNonreferenceSnap(tolerance);

            // UiSnapUtils用当前IUiView对ISnapContext做初始化。
            // Snapper示例中只是用当前工作平面作为SnapPlane
            bool doesInitSuccess = UiSnapUtils::InitSnapContextFromUiView(m_opSnapContext.get(), pCurrentView, PickPointExchangeData::SnapPlaneType::WorkPlane);
            DBG_WARN_AND_RETURN_FALSE_UNLESS(doesInitSuccess, L"UiSnapUtils设置ISnapContext失败",L"GDMP",L"2023-12-30");

            // 将工作平面的坐标设置为三维正交捕捉的坐标系
            IModelView* pModelView = pCurrentView->GetModelView();
            DBG_WARN_AND_RETURN_FALSE_UNLESS(pModelView, L"pModelView为空",L"GDMP",L"2023-12-30");
            const OwnerPtr<IPlane> opWorkPlane = pModelView->GetWorkPlane();
            DBG_WARN_AND_RETURN_FALSE_UNLESS(opWorkPlane, L"opWorkPlane为空",L"GDMP",L"2023-12-30");
            Coordinate3d coord = opWorkPlane->GetCoordinate();
            m_opSnapContext->SetCoordinate(coord);

            m_opSnapContext->SetInputPoint(pos);

            // 进行捕捉计算，并得到捕捉结果。获得排序最前的捕捉结果
            OwnerPtr<ISnapCandidates> opSnapCandidates = Snapper::Snap(m_opSnapContext.get());
            DBG_WARN_AND_RETURN_FALSE_UNLESS(opSnapCandidates, L"opSnapCandidates为空",L"GDMP",L"2023-12-30");
            const ISnap* pSnap = opSnapCandidates->GetCurrentSnap();
            Vector3d resultPoint = pos;

            if (pSnap)
            {
                // 捕捉成功，做捕捉渲染
                m_snappedPoint = pSnap->GetSnappedPoint();
                resultPoint = m_snappedPoint;
                ISnapRender* pSnapRender = ISnapRender::Get();
                DBG_WARN_AND_RETURN_FALSE_UNLESS(pSnapRender, L"pSnapRender为空",L"GDMP",L"2023-12-30");
                pSnapRender->Draw(pSnap, SnapTextPositionType::LowerRight);
                UpdateView();
            }
            else
            {
                // 如果没有捕捉到点，那么将鼠标垫按照相机方向投影到工作平面
                ActionBase::ProjectPositionOnWorkPlane(pCurrentView, pos, resultPoint);
                m_snappedPoint = resultPoint;
            }
            if (m_moveCallback)
            {
                // 回调函数，可以给父Action传递捕捉结果
                m_moveCallback(pCurrentView, resultPoint, pos);
            }
            return true;
        }

    private:
        // 缓存捕捉设置
        OwnerPtr<ISnapSettings> m_opSnapSettings;
        OwnerPtr<IPickFilter> m_opLocalPickFilter;
        OwnerPtr<IPickFilter> m_opRemotePickFilter;
        OwnerPtr<ISnapContext> m_opSnapContext;

        // 缓存测试用对话框
        SnapFilterDlg* m_snapFilterDlg;

        // 缓存捕捉结果和回调函数
        Vector3d* m_pFirstPoint;
        Vector3d& m_snappedPoint;
        MoveCallback m_moveCallback;
    };

    // 使用用Snapper包装的Action绘制模型线
    class DrawLineWithSnapperAction : public gcmp::ActionBase
    {
    public:
        // 绘制空间线的状态
        enum class DrawLinearStatus : int32_t
        {
            Not_Started,
            Getting_StartPoint,
            Getting_EndPoint
        };
    public:
        DrawLineWithSnapperAction()
        {
        }

        virtual void InitAction(IUiView* pCurrentView) override
        {
            EnterGettingStartPointState(pCurrentView);
        }

        virtual void OnChildActionFinished(IUiView* pCurrentView, const ActionOutput& childActionReturnParam) override
        {
            // 用户是否取消。这个值是SnapPointAction在MarkFinishStatus中返回给父Action的
            bool isUserCancelled = (childActionReturnParam.FinishStatus != ActionFinishStatus::Successful);

            // 如果结束的SnapPointAction是绘制模型线起点
            if (m_drawLinearStatus == DrawLinearStatus::Getting_StartPoint)
            {
                if (isUserCancelled)
                {
                    // 如果用户在画第一个点时就选择退出，那么就退出整个画线工具
                    CleanupTempGraphicsShape();// 记得删除临时线
                    MarkFinishStatus(ActionFinishStatus::Cancelled);
                    UpdateView();
                    return;
                }
                else
                {
                    // 如果用户绘制了起点，那么进入绘制模型线终点
                    EnterGettingEndPointState(pCurrentView);
                    return;
                }
            }
            else if (m_drawLinearStatus == DrawLinearStatus::Getting_EndPoint)
            {
                // 在绘制下一条模型线前，首先删除前一条模型线的临时线
                CleanupTempGraphicsShape();
                UpdateView();

                if (isUserCancelled)
                {
                    // 如果用户在画第二个点时选择退出，那么先退到画第一个点的状态
                    EnterGettingStartPointState(pCurrentView);
                    return;
                }
                else if (Vector3dUtils::IsEqual(m_ptStart, m_ptEnd, Constants::MIN_MODEL_SIZE))
                {
                    UiCommonDialog::ShowMessageBox(GBMP_TR(L"绘制模型线"), GBMP_TR(L"起始点和终止点不能相同"), (int)UiCommonDialog::ButtonType::OK);
                    EnterGettingEndPointState(pCurrentView);
                }
                else
                {
                    ElementId id = OnLineFinished(pCurrentView, m_ptStart, m_ptEnd);
                    if (id.IsValid())
                    {
                        // 如果模型线成功创建，刚结束的IPickPointAction绘制的点作为起点，开始绘制终点
                        m_ptStart = m_ptEnd;
                        EnterGettingEndPointState(pCurrentView);
                        return;
                    }
                    else
                    {
                        // 如果模型线创建失败，那么重新开始绘制起点
                        EnterGettingStartPointState(pCurrentView);
                        return;
                    }
                }
            }
        }

    private:
        /// 初始化和开始绘制点
        /// returnPoint：返回值的绘制点
        /// pFirstPoint：拾取中的第一个点，ISnapContext::SetFirstPoint()中可以用于临时尺寸的角度等
        void InitAndStartPickPointAction(Vector3d& returnPoint, Vector3d* pFirstPoint = nullptr)
        {
            // 设置SnapPointAction鼠标移动中捕捉到点的回调函数，启动SnapPointAction
            SnapPointAction::MoveCallback moveCallBack = SnapPointAction::CreateMoveCallback(&DrawLineWithSnapperAction::OnPointUpdate, this);
            OwnerPtr<SnapPointAction> opSnapPointAction = NEW_AS_OWNER_PTR(SnapPointAction, moveCallBack, returnPoint, pFirstPoint);
            DBG_WARN_AND_RETURN_VOID_UNLESS(opSnapPointAction, L"opSnapPointAction为空",L"GDMP",L"2023-12-30");
            StartChildAction(TransferOwnership(opSnapPointAction));
        }

        // 初始化和开始捕捉模型线的起点
        void EnterGettingStartPointState(gcmp::IUiView* pCurrentView)
        {
            m_drawLinearStatus = DrawLinearStatus::Getting_StartPoint;
            SetPromptMessage(GBMP_TR(L"请输入线式构件起始点"));
            InitAndStartPickPointAction(m_ptStart);
        }

        // 初始化和开始捕捉模型线的终点
        void EnterGettingEndPointState(gcmp::IUiView* pCurrentView)
        {
            m_drawLinearStatus = DrawLinearStatus::Getting_EndPoint;
            SetPromptMessage(GBMP_TR(L"请输入线式构件终止点"));
            InitAndStartPickPointAction(m_ptEnd, &m_ptStart);
        }

        void OnPointUpdate(gcmp::IUiView* pCurrentView, const gcmp::Vector3d& pickedPt, const gcmp::Vector3d& mousePos)
        {
            {
                // 在底部状态栏按照捕捉点坐标
                std::wstring tooltip = L"pickedPt: " + pickedPt.GetStringForDebug();
                SampleUIUtils::SetStatusBarPromptMessage(tooltip);
            }

            if (!Vector3dUtils::IsEqual(m_ptStart, pickedPt, Constants::MIN_MODEL_SIZE) && m_drawLinearStatus == DrawLinearStatus::Getting_EndPoint)
            {
                // 清空之前的临时线，绘制新的临时线
                CleanupTempGraphicsShape();
                OwnerPtr<IGraphicsElementShape> opTempGRep = IGraphicsElementShape::Create(GraphicsRenderLayer::TransientObject);
                DBG_WARN_AND_RETURN_VOID_UNLESS(opTempGRep, L"opTempGRep为空",L"GDMP",L"2023-12-30");
                OwnerPtr<ILine3d> opLine = ILine3d::Create(m_ptStart, pickedPt);
                DBG_WARN_AND_RETURN_VOID_UNLESS(opLine, L"opLine为空",L"GDMP",L"2023-12-30");
                opTempGRep->AddChild(TransferOwnership(IGraphicsCurve3d::Create(opLine.get())));

                // 获得当前视图工作平面的坐标系，用于临时尺寸初始化
                IModelView* pModelView = pCurrentView->GetModelView();
                DBG_WARN_AND_RETURN_VOID_UNLESS(pModelView, L"pModelView为空",L"GDMP",L"2023-12-30");
                const OwnerPtr<IPlane> opWorkPlane = pModelView->GetWorkPlane();
                DBG_WARN_AND_RETURN_VOID_UNLESS(opWorkPlane, L"opWorkPlane为空",L"GDMP",L"2023-12-30");
                Coordinate3d coord = opWorkPlane->GetCoordinate();

                // 创建临时尺寸
                OwnerPtr<ITemporaryDimensionContext> opTemporaryDimensionContext =
                    ITemporaryDimensionContext::CreateLinearContext(m_ptStart, pickedPt, true, true, coord);
                DBG_WARN_AND_RETURN_VOID_UNLESS(opTemporaryDimensionContext, L"opTemporaryDimensionContext为空",L"GDMP",L"2023-12-30");
                opTemporaryDimensionContext->SetValidRangeOfAngle(Intervald::Create(0.0, 360.0));
                opTemporaryDimensionContext->SetUserCustomInput(false);

                // 将临时尺寸和临时线一起绘制
                opTempGRep->AddChild(TransferOwnership(ITemporaryDimensionManager::Get()->GenerateTemporaryDimensionGraphics(opTemporaryDimensionContext.get())));
                opTempGRep->SetIsSelectable(false);
                opTempGRep->SetIsSnappable(false);

                DrawTempGraphicsElementShape(TransferOwnership(opTempGRep));
                pCurrentView->GetUiDocument()->UpdateView();// 不要忘记更新视图
            }
        }

        // 点击鼠标时创建模型线
        ElementId OnLineFinished(IUiView* pCurrentView, const Vector3d& startPt, const Vector3d& endPt)
        {
            IDocument *pDocument = pCurrentView->GetUiDocument()->GetDbDocument();
            DBG_WARN_AND_RETURN_UNLESS(pDocument, ElementId::InvalidID, L"pDocument为空",L"GDMP",L"2023-12-30");
            OwnerPtr<IUserTransaction> opUserTransaction =
                IUserTransaction::Create(pDocument, GBMP_TR(L"绘制模型线"));
            DBG_WARN_AND_RETURN_UNLESS(opUserTransaction, ElementId::InvalidID, L"创建opUserTransaction失败",L"GDMP",L"2023-12-30");

            OwnerPtr<ILine3d> opLine3d = ILine3d::Create(m_ptStart, m_ptEnd);
            DBG_WARN_AND_RETURN_UNLESS(opLine3d, ElementId::InvalidID, L"opLine3d为空",L"GDMP",L"2023-12-30");
            IModelLine* pModelLine = IModelLine::Create(pDocument, TransferOwnership(opLine3d));
            DBG_WARN_AND_RETURN_UNLESS(pModelLine, ElementId::InvalidID, L"pModelLine为空",L"GDMP",L"2023-12-30");

            opUserTransaction->Commit();

            IElement* pElem = pModelLine->GetOwnerElement();
            if (pElem)
            {
                return pElem->GetElementId();
            }
            return ElementId::InvalidID;
        }
    private:
        gcmp::Vector3d m_ptStart;   //捕捉到的起始点
        gcmp::Vector3d m_ptEnd;     //捕捉到的终止点

        DrawLinearStatus m_drawLinearStatus; //绘制线式构件的状态
    };
#pragma endregion 使用捕捉API的Action
}

#pragma region CmdSnapWithSnapper
REGISTER_COMMAND(CmdSnapWithSnapper)

CmdSnapWithSnapper::CmdSnapWithSnapper() : CommandBase(ID_CMD_SNAP_WITH_SNAPPER)
{
}

CmdSnapWithSnapper::~CmdSnapWithSnapper() {}

gcmp::OwnerPtr<gcmp::IAction> CmdSnapWithSnapper::ExecuteCommand(const gcmp::CommandParameters& cmdParams)
{
    if (SnapFilterDlg* dlg = SnapFilterDlg::Get())
    {
        dlg->show();
    }
    return NEW_AS_OWNER_PTR(DrawLineWithSnapperAction);
}

bool CmdSnapWithSnapper::IsEnabled() const
{
    return GcmpCommandState::IsInDocumentEnvironment() && (CmdSnapModuleEntry::GetSnapMethod() == SnapMethod::Snapper);
}

bool CmdSnapWithSnapper::IsVisible() const
{
    return GcmpCommandState::IsInDocumentEnvironment();
}
#pragma endregion CmdSnapWithSnapper

